<template>
	<div v-loading="isView" :class="['flow-containers system-page-background b-radius-4', { 'view-mode': isView }]">
		<div class="h-100% flex-col">
			<div class="p-10 flex justify-between" style="border-bottom: 1px solid rgb(218 218 218)">
				<div>
					<el-tooltip effect="dark" content="新建" placement="bottom">
						<el-button icon="circle-plus" @click="newDiagram" />
					</el-tooltip>
					<el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
						<el-button icon="Rank" @click="fitViewport" />
					</el-tooltip>
					<el-tooltip effect="dark" content="放大" placement="bottom">
						<el-button icon="zoom-in" @click="zoomViewport(true)" />
					</el-tooltip>
					<el-tooltip effect="dark" content="缩小" placement="bottom">
						<el-button icon="zoom-out" @click="zoomViewport(false)" />
					</el-tooltip>
					<el-tooltip effect="dark" content="后退" placement="bottom">
						<el-button icon="Back" @click="modeler.get('commandStack').undo()" />
					</el-tooltip>
					<el-tooltip effect="dark" content="前进" placement="bottom">
						<el-button icon="Right" @click="modeler.get('commandStack').redo()" />
					</el-tooltip>
				</div>

				<div>
					<el-button icon="View" @click="showXML">查看xml</el-button>
					<el-button icon="Download" @click="saveXML(true)">下载xml</el-button>
					<el-button icon="Picture" @click="saveImg(true)">下载svg</el-button>
					<el-button type="primary" @click="save">保存模型</el-button>
				</div>
			</div>

			<el-container class="h-0" style="background: #fff">
				<el-main style="padding: 0">
					<div ref="canvas" class="canvas" />
				</el-main>
				<el-aside style="width: 400px; background-color: #fafafa; border-left: 1px solid #dcdfe6" class="h-100%">
					<PropertyPanel ref="processFormRef" v-if="modeler" :modeler="modeler" :users="users" :groups="groups" :exps="exps" :categorys="categorys" />
				</el-aside>
			</el-container>
		</div>
	</div>
</template>

<script setup>
// 引入样式
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
// 汉化
import customTranslate from './common/customTranslate';
import Modeler from 'bpmn-js/lib/Modeler';
import PropertyPanel from './PropertyPanel';
import getInitStr from './flowable/init';
// 引入flowable的节点文件
import FlowableModule from './flowable/flowable.json';
import customControlsModule from './customPanel';

let processFormRef = ref(null);
const $emit = defineEmits(['showXML', 'save']),
	props = defineProps({
		xml: {
			type: String,
			default: ''
		},
		users: {
			type: Array,
			default: () => []
		},
		groups: {
			type: Array,
			default: () => []
		},
		categorys: {
			type: Array,
			default: () => []
		},
		exps: {
			type: Array,
			default: () => []
		},
		isView: {
			type: Boolean,
			default: false
		}
	});

watch(
	() => props.xml,
	(val) => {
		if (val) createNewDiagram(val);
	}
);

const canvas = ref(null),
	modeler = ref(null),
	zoom = ref(1);
onMounted(() => {
	// 生成实例
	modeler.value = new Modeler({
		container: canvas.value,
		additionalModules: [customControlsModule, { translate: ['value', customTranslate] }],
		moddleExtensions: {
			flowable: FlowableModule
		}
	});
	// 删除 bpmn logo  bpmn.io官方要求不给删或者隐藏，否则侵权
	document.querySelector('.bjs-powered-by').remove();
	modeler.value.createDiagram();

	// 新增流程定义
	if (!props.xml) {
		newDiagram();
	} else {
		createNewDiagram(props.xml);
	}
});

function newDiagram() {
	createNewDiagram(getInitStr());
}
// 让图能自适应屏幕
function fitViewport() {
	zoom.value = modeler.value.get('canvas').zoom('fit-viewport');
	const bbox = document.querySelector('.flow-containers .viewport').getBBox();
	const currentViewbox = modeler.value.get('canvas').viewbox();
	const elementMid = {
		x: bbox.x + bbox.width / 2 - 65,
		y: bbox.y + bbox.height / 2
	};
	modeler.value.get('canvas').viewbox({
		x: elementMid.x - currentViewbox.width / 2,
		y: elementMid.y - currentViewbox.height / 2,
		width: currentViewbox.width,
		height: currentViewbox.height
	});
	zoom.value = (bbox.width / currentViewbox.width) * 1.8;
}
// 放大缩小
function zoomViewport(zoomIn = true) {
	zoom.value = modeler.value.get('canvas').zoom();
	zoom.value += zoomIn ? 0.1 : -0.1;
	modeler.value.get('canvas').zoom(zoom.value);
}
async function createNewDiagram(data) {
	// 将字符串转换成图显示出来
	data = data.replace(/<!\[CDATA\[(.+?)]]>/g, (match, str) => str.replace(/</g, '&lt;'));
	try {
		await modeler.value.importXML(data);
		fitViewport();
	} catch (err) {
		console.error(err.message, err.warnings);
	}
}
// 对外 api
function getProcess() {
	const element = getProcessElement();
	return {
		id: element.id,
		name: element.name,
		category: element.$attrs['flowable:processCategory']
	};
}
function getProcessElement() {
	const rootElements = modeler.value.getDefinitions().rootElements;
	for (let i = 0; i < rootElements.length; i++) {
		if (rootElements[i].$type === 'bpmn:Process') return rootElements[i];
	}
}
async function saveXML(download = false) {
	try {
		const { xml } = await modeler.value.saveXML({ format: true });
		if (download) {
			downloadFile(`${getProcessElement().name}.bpmn20.xml`, xml, 'application/xml');
		}
		return xml;
	} catch (err) {
		console.log(err);
	}
}
async function showXML() {
	try {
		const xml = await saveXML();
		$emit('showXML', xml);
	} catch (err) {
		console.log(err);
	}
}
async function saveImg(download = false) {
	try {
		const { svg } = await modeler.value.saveSVG({ format: true });
		if (download) {
			downloadFile(getProcessElement().name, svg, 'image/svg+xml');
		}
		return svg;
	} catch (err) {
		console.log(err);
	}
}
async function save() {
	const process = getProcess();
	const xml = await saveXML();
	const svg = await saveImg();
	const result = { process, xml, svg };
	await processFormRef.value?.propertyValidate();
	$emit('save', result);
	window.parent.postMessage(result, '*');
}
function downloadFile(filename, data, type) {
	const a = document.createElement('a');
	const url = window.URL.createObjectURL(new Blob([data], { type: type }));
	a.href = url;
	a.download = filename;
	a.click();
	window.URL.revokeObjectURL(url);
}
</script>

<style lang="scss">
.view-mode {
	.el-header,
	.el-aside,
	.djs-palette,
	.bjs-powered-by {
		display: none;
	}

	.el-loading-mask {
		background-color: initial;
	}

	.el-loading-spinner {
		display: none;
	}
}

.flow-containers {
	width: 100%;
	height: 0;
	flex: 1;
	color: #000;
	.djs-context-pad.open {
		box-shadow: 2px 2px 10px var(--popup-shadow-color);
	}

	.canvas {
		width: 100%;
		height: 100%;
	}

	.panel {
		position: absolute;
		right: 0;
		top: 50px;
		width: 300px;
	}

	.load {
		margin-right: 10px;
	}

	.el-form-item__label {
		font-size: 13px;
	}

	.djs-palette {
		left: 0px !important;
		top: 0px;
		border-top: none;
		height: 100%;

		.two-column.open {
			width: 50px !important;
		}
	}

	.djs-direct-editing-parent {
		border-radius: 10px;
	}

	.djs-context-pad.open {
		display: block;
		background-color: var(--context-pad-entry-background-color);
	}

	.djs-container svg {
		min-height: 650px;
	}

	.highlight.djs-shape .djs-visual > :nth-child(1) {
		fill: green !important;
		stroke: green !important;
		fill-opacity: 0.2 !important;
	}

	.highlight.djs-shape .djs-visual > :nth-child(2) {
		fill: green !important;
	}

	.highlight.djs-shape .djs-visual > path {
		fill: green !important;
		fill-opacity: 0.2 !important;
		stroke: green !important;
	}

	.highlight.djs-connection > .djs-visual > path {
		stroke: green !important;
	}

	.highlight-todo.djs-connection > .djs-visual > path {
		stroke: orange !important;
		stroke-dasharray: 4px !important;
		fill-opacity: 0.2 !important;
	}

	.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
		fill: orange !important;
		stroke: orange !important;
		stroke-dasharray: 4px !important;
		fill-opacity: 0.2 !important;
	}

	.overlays-div {
		font-size: 10px;
		color: red;
		width: 100px;
		top: -20px !important;
	}
	.djs-label {
		transform: translate(-45px, 0);
	}
}

#helper-svg {
	display: none;
}
.bjs-align-elements-menu-entry {
	> img {
		width: 30px;
	}
}
.flow-containers .djs-container svg {
	min-height: 0;
}
</style>
